// Copyright (C) 2010  Joan Puigcerver Perez, <joapuipe@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include <People.hpp>

#include <cstring>
#include <algorithm>

#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/OutOfMemoryException.hpp>

#include <SAXPeopleHandler.hpp>

static const char to_red_ascii[][10] = { 
  " ", "", "c", "p", "", "y", "", "", "", "(c)", 
  "a", "", "not", " ", "(r)", "", "o", "", "2", "3", 
  "", "u", "", "", "", "1", "o", "", "14", "12", 
  "34", "", "a", "a", "a", "a", "a", "a", "ae", "c", "e", 
  "e", "e", "e", "i", "i", "i", "i", "d", "ny", "o", "o",
  "o", "o", "o", "x", "0", "u", "u", "u", "u", "y", "th", 
  "ss", "a", "a", "a", "a", "a", "a", "ae", "c", "e", "e", "e", 
  "e", "i", "i", "i", "i", "d", "ny", "o", "o", "o", "o", "o", 
  "", "0", "u", "u", "u", "u", "y", "th", "y" 
};

// get a reduced ascii string. Only this characters: a-z, 0-9, space, '('  and ')'.
static std::string reduced_ascii(const std::string& str)
{
  std::string result;
  for(std::string::const_iterator c = str.begin(); c != str.end(); ++c) {
    uint16_t utf8_char = *c;
    if ( *c < 0 ) {
      utf8_char <<= 8;
      utf8_char |= 0x00FF & *(++c);
      
      if (utf8_char >= 0xC2A0 && utf8_char <= 0xC2BF )
	result += to_red_ascii[utf8_char - 0xC2A0];
      else if (utf8_char >= 0xC380 && utf8_char <= 0xC3BF)
	result += to_red_ascii[utf8_char - 0xC380 + 0x20];     
    } else if ( *c >= 'A' && *c <= 'Z' )
      result += tolower(*c);
    else if ( *c == ' ' || *c == '(' || *c == ')' || 
	      (*c >= '0' && *c <= '9') || (*c >= 'a' && *c <= 'z') )
      result += *c;
  }
  
  return result;
}

People::People(std::ostream& logf, ost::Mutex& logm)
  : logfile(logf), log_mutex(logm), num_persons(0)
{
  
}

People::~People()
{
  for(std::vector<Person*>::iterator it = people_container.begin();
      it!=people_container.end();++it)
    delete *it;
}

bool People::load(Database * database)
{ 
  SAX2XMLReader* parser = XMLReaderFactory::createXMLReader();
  
  SAXPeopleHandler handler(database, logfile, log_mutex);
  parser->setContentHandler(&handler);
  parser->setErrorHandler(&handler);
  
  try {
    parser->parse(PEOPLE_XML);
  }
  catch (const XMLException& toCatch) {
    char* message = XMLString::transcode(toCatch.getMessage());
    print_log_line("Exception message is:", logfile, log_mutex);
    print_log_line(message, logfile, log_mutex);
    XMLString::release(&message);
    delete parser;
    return false;
  }
  catch (const SAXParseException& toCatch) {
    char* message = XMLString::transcode(toCatch.getMessage());
    print_log_line("Exception message is:", logfile, log_mutex);
    print_log_line(message, logfile, log_mutex);;
    XMLString::release(&message);
    delete parser;
    return false;
  }
  catch (const OutOfMemoryException& toCatch) {
    char *message = XMLString::transcode(toCatch.getMessage());
    print_log_line("Exception message is:", logfile, log_mutex);
    print_log_line(message, logfile, log_mutex);
    XMLString::release(&message);
    delete parser;
    return false;
  }
  catch (...) {
    print_log_line("Unexpected exception!", logfile, log_mutex);
    if ( handler.curr_person == NULL ) {
      print_log_line("Null person!", logfile, log_mutex);
    } else {
      print_log_line(handler.curr_person->name(), logfile, log_mutex);
    }
    delete parser;
    return false;
  }
  
  delete parser;
  return true;
}

Person * People::add_person(const std::string& name)
{
  std::string ascii_name = reduced_ascii(name);
  std::pair<bool,std::vector<Person *> > res = people.exact_lookup(ascii_name);
  Person * person;

  if ( res.first == true ) {
    // check if some other person use this string as the identifier
    for(RT_Terminal::const_iterator it = res.second.begin(); it != res.second.end(); ++it)
      if ( name == (*it)->name() ) return NULL;

    person = new Person(num_persons, name);
    res.second.push_back(person);
    people.insert(ascii_name, res.second);
  } else {
    person = new Person(num_persons, name);
    std::vector<Person*> nv(1, person);
    people.insert(ascii_name, nv);
  }
 
  num_persons++;
  people_container.push_back(person);
  return person;
}

bool People::add_aka(const std::string& aka, Person* person)
{
  string ascii_aka = reduced_ascii(aka);
  std::pair<bool, std::vector<Person *> > res = people.exact_lookup(ascii_aka);

  if ( res.first == true ) {
    // check if some other person use this string as the identifier
    for(RT_Terminal::const_iterator it = res.second.begin(); it != res.second.end(); ++it)
      if( aka == (*it)->name() ) return false;

    res.second.push_back(person);
    people.insert(ascii_aka, res.second);
  } else {
    std::vector<Person*> nv(1, person);
    people.insert(ascii_aka, nv);
  }

  person->add_aka(aka);
  return true;
}

class CompPairFst
{
 public:
  bool operator()(const People::TSearchItem& a, const People::TSearchItem& b)
  { return (a.first < b.first); }
};

People::TSearch
People::search(const std::string& name) const
{
  std::vector<TSearchItem> matched_persons;
  string ascii_name = reduced_ascii(name);

  std::pair<bool, std::vector<Person *> > exact_res = people.exact_lookup(ascii_name);
  if ( exact_res.first ) {
    for(std::vector<Person*>::const_iterator it = exact_res.second.begin(); it != exact_res.second.end(); ++it)
      matched_persons.push_back( TSearchItem(*it, ascii_name) );
  } else {
    size_t maxerrs = std::min((size_t)3, name.length()/3);
    std::vector< std::pair<std::vector<Person *>, std::string > > res = people.approximate_lookup(ascii_name, maxerrs);
    
    for(std::vector< std::pair<std::vector<Person *>, std::string > >::const_iterator it = res.begin(); res.end() != it; ++it)
      for(std::vector<Person *>::const_iterator it2 = it->first.begin(); it2 != it->first.end(); ++it2)
	matched_persons.push_back( TSearchItem(*it2, it->second) );
  }
  
  std::sort(matched_persons.begin(), matched_persons.end(), CompPairFst());
  std::vector<TSearchItem>::iterator it = std::unique(matched_persons.begin(), matched_persons.end());
  matched_persons.resize(it - matched_persons.begin());
  
  bool exact = false;
  for(it = matched_persons.begin(); it != matched_persons.end(); ++it)
    if ( (*it).first->name() == name || find((*it).first->akas().begin(), (*it).first->akas().end(), name) != (*it).first->akas().end() )
      exact = true;
  
  return TSearch(exact, matched_persons);
}

const Person * People::search_id(const std::string& name) const
{
  string ascii_name = reduced_ascii(name);
  std::pair< bool, std::vector<Person*> > exact_res = people.exact_lookup(ascii_name);
  if ( !exact_res.first ) return NULL;

  for(std::vector<Person*>::const_iterator it = exact_res.second.begin(); it != exact_res.second.end(); ++it)
    if ( (*it)->name() == name ) return *it;

  return NULL;
}

size_t People::size(void) const
{
  return num_persons;
}
